#include "SPI.h"

SPIClass SPI;

SPIClass::SPIClass()
{
    _spi.pin_miso = digitalPinToPinName(MISO);
    _spi.pin_mosi = digitalPinToPinName(MOSI);
    _spi.pin_sclk = digitalPinToPinName(SCK);
    _spi.pin_ssel = NC;
}

SPIClass::SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel)
{
    _spi.pin_miso = digitalPinToPinName(miso);
    _spi.pin_mosi = digitalPinToPinName(mosi);
    _spi.pin_sclk = digitalPinToPinName(sclk);
    _spi.pin_ssel = digitalPinToPinName(ssel);
}

void SPIClass::begin(uint8_t _pin) 
{
    uint8_t idx = pinIdx(_pin, ADD_NEW_PIN );
    if (idx >= NB_SPI_SETTINGS) 
        return;
    
    if ((_pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC)) {
        pinMode(_pin, OUTPUT);
        digitalWrite(_pin, HIGH);
    }

    spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].bOrder);
    _CSPinConfig = _pin;
}

void SPIClass::beginTransaction(uint8_t pin, SPISettings settings)
{
    uint8_t idx;
    if (pin > NUM_DIGITAL_PINS)
        return;
    idx = pinIdx(pin, ADD_NEW_PIN);
    if (idx >= NB_SPI_SETTINGS)
        return;

    spiSettings[idx].clk = settings.clk;
    spiSettings[idx].dMode = settings.dMode;
    spiSettings[idx].bOrder = settings.bOrder;
    spiSettings[idx].noReceive = settings.noReceive;

    if ((pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC))
    {
        pinMode(pin, OUTPUT);
        digitalWrite(pin, HIGH);
    }

    spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].bOrder);
    _CSPinConfig = pin;
}

void SPIClass::endTransaction(uint8_t pin)
{
    if (pin > NUM_DIGITAL_PINS)
        return;

    RemovePin(pin);
    _CSPinConfig = NO_CONFIG;
}

byte SPIClass::transfer(uint8_t pin, uint8_t _data, SPITransferMode _mode)
{
    uint8_t rx_buffer = 0;
    if (pin > NUM_DIGITAL_PINS)
        return rx_buffer;

    uint8_t idx = pinIdx(pin, GET_IDX);
    if (pin != _CSPinConfig)
    {
        if (idx >= NB_SPI_SETTINGS)
            return rx_buffer;

        spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].bOrder);
        _CSPinConfig = pin;
    }

    if ((pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC))
    {
        digitalWrite(pin, LOW);
    }

    spi_transfer(&_spi, &_data, &rx_buffer, sizeof(uint8_t), SPI_TRANSFER_TIMEOUT, spiSettings[idx].noReceive);

    if ((pin != CS_PIN_CONTROLLED_BY_USER) && (_mode == SPI_LAST) && (_spi.pin_ssel == NC))
    {
        digitalWrite(pin, HIGH);
    }

    return rx_buffer;
}

uint16_t SPIClass::transfer16(uint8_t pin, uint16_t _data, SPITransferMode _mode)
{
    uint16_t rx_buffer = 0;
    uint16_t tmp;
    if (pin > NUM_DIGITAL_PINS)
        return rx_buffer;

    uint8_t idx = pinIdx(pin, GET_IDX);
    if (pin != _CSPinConfig)
    {
        if (idx >= NB_SPI_SETTINGS)
            return rx_buffer;

        spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].bOrder);
        _CSPinConfig = pin;
    }

    if (spiSettings[idx].bOrder)
    {
        tmp = ((_data & 0xFF00) >> 8) | ((_data & 0xFF) << 8);
        _data = tmp;
    }
    if ((pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC))
    {
        digitalWrite(pin, LOW);
    }

    spi_transfer(&_spi, (uint8_t *)&_data, (uint8_t *)&rx_buffer, sizeof(uint16_t), SPI_TRANSFER_TIMEOUT, spiSettings[idx].noReceive);

    if ((pin != CS_PIN_CONTROLLED_BY_USER) && (_mode == SPI_LAST) && (_spi.pin_ssel == NC))
    {
        digitalWrite(pin, HIGH);
    }
    if (spiSettings[idx].bOrder)
    {
        tmp = ((rx_buffer & 0xFF00) >> 8) | ((rx_buffer & 0xFF) << 8);
        rx_buffer = tmp;
    }
    return rx_buffer;
}

void SPIClass::transfer(uint8_t pin, void *_buf, size_t _count, SPITransferMode _mode)
{
    if ((_count == 0) || (_buf == NULL) || (pin > NUM_DIGITAL_PINS))
        return;

    uint8_t idx = pinIdx(pin, GET_IDX);
    if (pin != _CSPinConfig)
    {
        if (idx >= NB_SPI_SETTINGS)
            return;

        spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].bOrder);
        _CSPinConfig = pin;
    }

    if ((pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC))
    {
        digitalWrite(pin, LOW);
    }

    spi_transfer(&_spi, (uint8_t *)&_buf, (uint8_t *)&_buf, _count, SPI_TRANSFER_TIMEOUT, spiSettings[idx].noReceive);

    if ((pin != CS_PIN_CONTROLLED_BY_USER) && (_mode == SPI_LAST) && (_spi.pin_ssel == NC))
    {
        digitalWrite(pin, HIGH);
    }
}

void SPIClass::transfer(byte _pin, void *_bufout, void *_bufin, size_t _count, SPITransferMode _mode)
{
    if ((_count == 0) || (_bufout == NULL) || (_bufin == NULL) || (_pin > NUM_DIGITAL_PINS))
        return;

    uint8_t idx = pinIdx(_pin, GET_IDX);
    if (_pin != _CSPinConfig)
    {
        if (idx >= NB_SPI_SETTINGS)
            return;

        spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].bOrder);
        _CSPinConfig = _pin;
    }

    if ((_pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC))
    {
        digitalWrite(_pin, LOW);
    }

    spi_transfer(&_spi, (uint8_t *)&_bufout, (uint8_t *)&_bufin, _count, SPI_TRANSFER_TIMEOUT, spiSettings[idx].noReceive);

    if ((_pin != CS_PIN_CONTROLLED_BY_USER) && (_mode == SPI_LAST) && (_spi.pin_ssel == NC))
    {
        digitalWrite(_pin, HIGH);
    }
}

void SPIClass::setBitOrder(uint8_t _pin, BitOrder _bitOrder)
{
    if (_pin > NUM_DIGITAL_PINS)
        return;
    uint8_t idx = pinIdx(_pin, GET_IDX);

    if (idx >= NB_SPI_SETTINGS)
        return;
    spiSettings[idx].bOrder = _bitOrder;

    spi_init(&_spi, spiSettings[idx].clk,
             spiSettings[idx].dMode,
             spiSettings[idx].bOrder);
}

void SPIClass::setDataMode(uint8_t _pin, uint8_t _mode)
{
    if (_pin > NUM_DIGITAL_PINS)
        return;
    uint8_t idx = pinIdx(_pin, GET_IDX);

    if (idx >= NB_SPI_SETTINGS)
        return;

    if (SPI_MODE0 == _mode)
    {
        spiSettings[idx].dMode = SPI_MODE_0;
    }
    else if (SPI_MODE1 == _mode)
    {
        spiSettings[idx].dMode = SPI_MODE_1;
    }
    else if (SPI_MODE2 == _mode)
    {
        spiSettings[idx].dMode = SPI_MODE_2;
    }
    else if (SPI_MODE3 == _mode)
    {
        spiSettings[idx].dMode = SPI_MODE_3;
    }

    spi_init(&_spi, spiSettings[idx].clk,
             spiSettings[idx].dMode,
             spiSettings[idx].bOrder);
}

void SPIClass::setClockDivider(uint8_t _pin, uint8_t _divider)
{
    return;
    uint8_t idx = pinIdx(_pin, GET_IDX);

    if (idx >= NB_SPI_SETTINGS)
        return;

    if (_divider == 0)
    {
        spiSettings[idx].clk = SPI_SPEED_CLOCK_DEFAULT;
    }
    else
    {
        spiSettings[idx].clk = spi_getClkFreq(&_spi) / _divider;
    }

    spi_init(&_spi, spiSettings[idx].clk,
             spiSettings[idx].dMode,
             spiSettings[idx].bOrder);
}
